#ifndef RAPIDJSON_PRETTYWRITER_H_
#define RAPIDJSON_PRETTYWRITER_H_

#include "writer.h"

namespace rapidjson
{

//! Writer with indentation and spacing.
    /*!
    	\tparam Stream Type of ouptut stream.
    	\tparam Encoding Encoding of both source strings and output.
    	\tparam Allocator Type of allocator for allocating memory of stack.
    */
    template<typename Stream, typename Encoding = UTF8<>, typename Allocator = MemoryPoolAllocator<> >
    class PrettyWriter : public Writer<Stream, Encoding, Allocator>
    {
    public:
        typedef Writer<Stream, Encoding, Allocator> Base;
        typedef typename Base::Ch Ch;

        //! Constructor
        /*! \param stream Output stream.
        	\param allocator User supplied allocator. If it is null, it will create a private one.
        	\param levelDepth Initial capacity of
        */
        PrettyWriter(Stream& stream, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) :
            Base(stream, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {}

        //! Set custom indentation.
        /*! \param indentChar		Character for indentation. Must be whitespace character (' ', '\t', '\n', '\r').
        	\param indentCharCount	Number of indent characters for each indentation level.
        	\note The default indentation is 4 spaces.
        */
        PrettyWriter& SetIndent(Ch indentChar, unsigned indentCharCount)
        {
            RAPIDJSON_ASSERT(indentChar == ' ' || indentChar == '\t' || indentChar == '\n' || indentChar == '\r');
            indentChar_ = indentChar;
            indentCharCount_ = indentCharCount;
            return *this;
        }

        //@name Implementation of Handler.
        //@{

        PrettyWriter& Null()
        {
            PrettyPrefix(kNullType);
            Base::WriteNull();
            return *this;
        }
        PrettyWriter& Bool(bool b)
        {
            PrettyPrefix(b ? kTrueType : kFalseType);
            Base::WriteBool(b);
            return *this;
        }
        PrettyWriter& Int(int i)
        {
            PrettyPrefix(kNumberType);
            Base::WriteInt(i);
            return *this;
        }
        PrettyWriter& Uint(unsigned u)
        {
            PrettyPrefix(kNumberType);
            Base::WriteUint(u);
            return *this;
        }
        PrettyWriter& Int64(int64_t i64)
        {
            PrettyPrefix(kNumberType);
            Base::WriteInt64(i64);
            return *this;
        }
        PrettyWriter& Uint64(uint64_t u64)
        {
            PrettyPrefix(kNumberType);
            Base::WriteUint64(u64);
            return *this;
        }
        PrettyWriter& Double(double d)
        {
            PrettyPrefix(kNumberType);
            Base::WriteDouble(d);
            return *this;
        }

        PrettyWriter& String(const Ch* str, SizeType length, bool copy = false)
        {
            (void)copy;
            PrettyPrefix(kStringType);
            Base::WriteString(str, length);
            return *this;
        }

        PrettyWriter& StartObject()
        {
            PrettyPrefix(kObjectType);
            new(Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(false);
            Base::WriteStartObject();
            return *this;
        }

        PrettyWriter& EndObject(SizeType memberCount = 0)
        {
            (void)memberCount;
            RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level));
            RAPIDJSON_ASSERT(!Base::level_stack_.template Top<typename Base::Level>()->inArray);
            bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;

            if(!empty)
            {
                Base::stream_.Put('\n');
                WriteIndent();
            }

            Base::WriteEndObject();
            return *this;
        }

        PrettyWriter& StartArray()
        {
            PrettyPrefix(kArrayType);
            new(Base::level_stack_.template Push<typename Base::Level>()) typename Base::Level(true);
            Base::WriteStartArray();
            return *this;
        }

        PrettyWriter& EndArray(SizeType memberCount = 0)
        {
            (void)memberCount;
            RAPIDJSON_ASSERT(Base::level_stack_.GetSize() >= sizeof(typename Base::Level));
            RAPIDJSON_ASSERT(Base::level_stack_.template Top<typename Base::Level>()->inArray);
            bool empty = Base::level_stack_.template Pop<typename Base::Level>(1)->valueCount == 0;

            if(!empty)
            {
                Base::stream_.Put('\n');
                WriteIndent();
            }

            Base::WriteEndArray();
            return *this;
        }

        //@}

        //! Simpler but slower overload.
        PrettyWriter& String(const Ch* str)
        {
            return String(str, internal::StrLen(str));
        }

    protected:
        void PrettyPrefix(Type type)
        {
            (void)type;

            if(Base::level_stack_.GetSize() != 0)    // this value is not at root
            {
                typename Base::Level* level = Base::level_stack_.template Top<typename Base::Level>();

                if(level->inArray)
                {
                    if(level->valueCount > 0)
                    {
                        Base::stream_.Put(','); // add comma if it is not the first element in array
                        Base::stream_.Put('\n');
                    }
                    else
                        Base::stream_.Put('\n');

                    WriteIndent();
                }
                else  	// in object
                {
                    if(level->valueCount > 0)
                    {
                        if(level->valueCount % 2 == 0)
                        {
                            Base::stream_.Put(',');
                            Base::stream_.Put('\n');
                        }
                        else
                        {
                            Base::stream_.Put(':');
                            Base::stream_.Put(' ');
                        }
                    }
                    else
                        Base::stream_.Put('\n');

                    if(level->valueCount % 2 == 0)
                        WriteIndent();
                }

                if(!level->inArray && level->valueCount % 2 == 0)
                    RAPIDJSON_ASSERT(type == kStringType);  // if it's in object, then even number should be a name

                level->valueCount++;
            }
            else
                RAPIDJSON_ASSERT(type == kObjectType || type == kArrayType);
        }

        void WriteIndent()
        {
            size_t count = (Base::level_stack_.GetSize() / sizeof(typename Base::Level)) * indentCharCount_;
            PutN(Base::stream_, indentChar_, count);
        }

        Ch indentChar_;
        unsigned indentCharCount_;
    };

} // namespace rapidjson

#endif // RAPIDJSON_RAPIDJSON_H_
